package gameStates.network;

import gameStates.absGamesStates.network.AbsClientState;
import gameStates.gui.HUDState;
import interfaces.hud.BasicHUD;
import interfaces.hud.chat.ChatType;
import interfaces.hud.pilot.PilotHUD;
import interfaces.menu.joinGame.gamesTable.GamesTableWidget;

import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;

import main.InitGame;
import map.spawnStation.SpawnPosition;
import map.spawnStation.SpawnStation;
import networking.ClientSyncManager;
import networking.SyncManager;
import networking.Synchronizable;
import networking.messages.ChatMessage;
import networking.messages.GameStartMessage;
import networking.messages.HunterRegisterMessage;
import networking.messages.JoinMessage;
import networking.messages.PingMessage;
import networking.messages.SimpleMessage;
import networking.messages.SquadUpdateMessage;
import networking.messages.TimeSyncMessage;
import networking.messages.createMessages.MSInfoMessage;
import networking.messages.playerMessages.PlayerAckMessage;
import networking.messages.playerMessages.PlayerAddMessage;
import networking.messages.playerMessages.PlayerAwardMessage;
import networking.messages.playerMessages.PlayerKillMessage;
import networking.messages.playerMessages.PlayerRegisterMessage;
import networking.messages.playerMessages.TeamChangeAnswerMessage;
import networking.messages.playerMessages.TeamChangeWantMessage;
import networking.messages.playerMessages.PlayerUpdateMessage;
import networking.messages.syncMessages.CMUseMessage;
import networking.messages.syncMessages.CollisionDamageMessage;
import networking.messages.syncMessages.NearStationChangeMessage;
import networking.messages.syncMessages.NodeSyncMessage;
import networking.messages.syncMessages.SpawnStationSyncMessage;
import networking.messages.syncMessages.UpgradeMessage;
import networking.messages.teamMessages.ProcessCreateMessage;
import networking.messages.teamMessages.ProcessUpdateMessage;
import networking.messages.teamMessages.TeamValueMessage;
import networking.messages.weaponMessages.WeaponAddMessage;
import networking.messages.weaponMessages.WeaponFireAddMessage;
import networking.messages.weaponMessages.WeaponFireRemoveMessage;
import networking.messages.weaponMessages.WeaponRemoveMessage;
import networking.messages.weaponMessages.WeaponRequestMessage;

import logic.common.Squad;
import logic.common.SquadChangedEvent;
import logic.common.SquadListener;
import logic.common.player.Player;
import logic.common.player.awards.Award;
import logic.common.player.awards.AwardManager;
import logic.common.player.playerListeners.KillEvent;
import logic.common.player.playerListeners.NearStationChangedEvent;
import logic.common.team.Team;
import logic.common.team.processes.TeamProcess;
import logic.common.team.processes.TeamProcessFactory;
import logic.common.team.processes.TeamProcess.ProcessType;
import logic.ships.Ship;
import logic.ships.hunter.Hunter;
import logic.ships.mothership.MotherShip;
import logic.ships.moveableShip.MovableShip;
import logic.status.StatusChangedEvent;
import logic.status.StatusListener;
import logic.weapons.Weapon;
import logic.weapons.WeaponFire;
import logic.weapons.WeaponType;
import logic.weapons.projectileWeapons.heatSeeker.HeatSeeker;

import com.captiveimagination.jgn.clientserver.JGNConnection;
import com.captiveimagination.jgn.message.Message;
import com.captiveimagination.jgn.synchronization.message.SynchronizeCreateMessage;
import com.jme.math.FastMath;
import com.jme.util.GameTaskQueueManager;

public class ClientState extends NetworkState implements AbsClientState {
			
	private ClientSyncManager clientManager;
	private short currentPlayerID;
	private GamesTableWidget lastGameWidget;
	
	private int currentWeaponFireClientID;
	private HashMap<Integer, WeaponFire> clientWeaponFireMap;
	
	public ClientState() {
		super("client", false);
	}
	
	@Override
	protected void initStructures() {
		super.initStructures();
		clientWeaponFireMap = new HashMap<Integer, WeaponFire>();
		currentWeaponFireClientID = 0;
	}
		
	@Override
	public void update(float tpf) {}
	
	@Override
	public void cleanup() {
		super.cleanup();
		if(clientManager != null) clientManager.endClient();
		clientManager = null;
	}
	
	@Override
	public void connected(JGNConnection connection) {
		currentPlayerID = connection.getPlayerId();
		
		clientManager.sendMessageToServer(new JoinMessage(currentPlayerID));
	}
	
	@Override
	public void disconnected(JGNConnection connection) {
		InitGame.get().endCurrentGame();
	}
	
	@Override
	public void messageCertified(Message message) {
		
	}
	
	@Override
	public void messageFailed(Message message) {
		
	}
	
	@Override
	public void messageReceived(Message message) {
		if(ingameState == null || ingameState.getGame() == null 
		|| (ingameState.getCam() == null 
		&& (message instanceof WeaponFireAddMessage || message instanceof WeaponFireRemoveMessage))) {
			return;
		} else if(message instanceof GameStartMessage) {
			applyGameStartMessage((GameStartMessage)message);
			clientManager.sendMessageToServer(new PlayerRegisterMessage(ingameState.getPlayer()));
		} else if(message instanceof WeaponRemoveMessage) {
			removeWeaponFromShip(((WeaponRemoveMessage)message).getWeaponIndex());
		} else if(message instanceof WeaponAddMessage) {
			createNewWeapon((WeaponAddMessage)message);
		} else if(message instanceof WeaponFireAddMessage) {
			WeaponFireAddMessage wfam = (WeaponFireAddMessage)message;
			if(wfam.getWeaponFireClientID() != 0) {
				WeaponFire fire = clientWeaponFireMap.get(Integer.valueOf(wfam.getWeaponFireClientID()));
				if(fire != null) fire.setID(wfam.getWeaponFireID());
			} else fireWeapon(wfam, true);
		} else if(message instanceof WeaponRequestMessage) {
			WeaponRequestMessage wrm = (WeaponRequestMessage)message;
			
			List<Integer> weapons = getWeaponsOfShip(wrm.getShipIndex());
			for(Integer weaponIndex : weapons) {
				WeaponAddMessage wam = new WeaponAddMessage(getWeapon(weaponIndex.intValue()));
				clientManager.sendMessageToServer(wam);
			}
		} else if(message instanceof WeaponFireRemoveMessage) {
			final WeaponFireRemoveMessage wfrm = (WeaponFireRemoveMessage)message;
			final WeaponFire fire = weaponFires.get(wfrm.getWeaponFireID());
			if(fire != null) {
				final Ship ship = (Ship)objects.get(wfrm.getHunterID());
				GameTaskQueueManager.getManager().update(new Callable<Void>() {
					@Override
					public Void call() throws Exception {
						fire.impact(wfrm.getLoc());
						if(ship != null && ship instanceof MovableShip && !ship.isDestroyed()) {
							MovableShip movableShip = (MovableShip)ship;
							movableShip.applyWeaponFireRemoveMessage(wfrm);
							movableShip.receiveDamage(fire);
						}
						return null;
					}
				});
			}
		} else if(message instanceof TimeSyncMessage) {
			TimeSyncMessage tsm = (TimeSyncMessage)message;
			int time = tsm.getTime();
			switch(tsm.getType()) {
				case Game: ingameState.updateGameTime(time); break;
				case End: ingameState.updateEndTime(time); break;
				case Spawn: 
					GameTaskQueueManager.getManager().update(new Callable<Void>() {
						@Override
						public Void call() throws Exception {
							ingameState.updateRespawnTime();
							return null;
						}
					});
			}
			
		} else if(message instanceof PlayerAddMessage) {
			PlayerAddMessage pam = (PlayerAddMessage)message;
			registerNewPlayer(pam);
		} else if(message instanceof PlayerUpdateMessage) {
			PlayerUpdateMessage pum = (PlayerUpdateMessage)message;
			applyPlayerUpdateMessage(pum);
		} else if(message instanceof PlayerAckMessage) {
			PlayerAckMessage packm = (PlayerAckMessage)message;
			int id = packm.getPlayerID();
			
			Player player = ingameState.getPlayer();
			player.setID(id);
			
			players.register(player, id);
			
			addListener(player, createPlayerListener());
			addListener(player, getNewPlayerValueListener(id));
			addListener(player, createPlayerStationListener(id));
		} else if(message instanceof HunterRegisterMessage) {
			HunterRegisterMessage hrm = (HunterRegisterMessage)message;
			int hunterID = hrm.getShipID();
			Hunter hunter = ingameState.getPlayer().getHunter();
			objects.register(hunter, hunterID);
			StatusListener l = getPlayerStatusListener(hunterID);
			addListener(hunter, l);
		} else if(message instanceof MSInfoMessage) {
			MSInfoMessage msim = (MSInfoMessage)message;
			MotherShip ms = (MotherShip)objects.get(msim.getShipID());
			registerTransmittedShip(ms);
		} else if(message instanceof NodeSyncMessage) {
			handleNodeSyncMessage((NodeSyncMessage)message);
		} else if(message instanceof UpgradeMessage) {
			handleUpgradeMessage((UpgradeMessage)message);
		} else if(message instanceof TeamChangeAnswerMessage) {
			final TeamChangeAnswerMessage tcam = (TeamChangeAnswerMessage)message;
			
			GameTaskQueueManager.getManager().update(new Callable<Void>() {
				@Override
				public Void call() throws Exception {
					Player player = getPlayer(tcam.getPlayerID());
					
					if(tcam.getAnswer()) player.switchTeam(player.getEnemyTeam());
					else if(player == ingameState.getPlayer()) player.teamChangeFailed();
					return null;
				}
			});
		} else if(message instanceof SpawnStationSyncMessage) {
			applySpawnStationSyncMessage((SpawnStationSyncMessage)message);
		} else if(message instanceof NearStationChangeMessage) {
			applyNearStationChangeMessage((NearStationChangeMessage)message);
		} else if(message instanceof SquadUpdateMessage) {
			applySquadUpdateMessage((SquadUpdateMessage)message);
		} else if(message instanceof CMUseMessage) {
			handleCMUseMessage((CMUseMessage)message);
		} else if(message instanceof ChatMessage) {
			applyChatMessage((ChatMessage)message);
		} else if(message instanceof PlayerKillMessage) {
			applyPlayerKillMessage((PlayerKillMessage)message);
		} else if(message instanceof PlayerAwardMessage) {
			applyPlayerAwardMessage((PlayerAwardMessage)message);
		} else if(message instanceof PingMessage) {
			sendMessage(new PingMessage(((PingMessage)message).getServerTime()));
		} else if(message instanceof ProcessCreateMessage) {
			applyProcessCreateMessage((ProcessCreateMessage)message);
		} else if(message instanceof ProcessUpdateMessage) {
			applyProcessUpdateMessage((ProcessUpdateMessage)message);
		} else if(message instanceof TeamValueMessage) {
			TeamValueMessage tvm = (TeamValueMessage)message;
			Team team = ingameState.getGame().getTeam(tvm.getFraction());
			team.applyTeamValueMessage(tvm);
		}
	}
	
	private void applyProcessCreateMessage(ProcessCreateMessage pcm) {
		ProcessType type = pcm.getProcessType();
		Team team = ingameState.getGame().getTeam(pcm.getFraction());
		WeaponType weaponType = pcm.getWeaponType();
		TeamProcess process = TeamProcessFactory.createProcess(type, team, weaponType);
		process.setDummy();
		teamProcesses.register(process, pcm.getProcessID());
		team.addTeamProcess(process);
	}
	
	private void applyProcessUpdateMessage(ProcessUpdateMessage pum) {
		TeamProcess process = teamProcesses.get(pum.getProcessID());
		if(process != null) {
			process.updateFromPercentage(pum.getPercentage());
		}
	}
	
	private void applyPlayerAwardMessage(PlayerAwardMessage pam) {
		Player player = getPlayer(pam.getPlayerID());
		if(player != null) {
			Award award = AwardManager.createAward(player, pam.getAwardType());
			player.addAward(award);
		}
	}
	
	private void applyPlayerKillMessage(PlayerKillMessage pkm) {
		Player killer = getPlayer(pkm.getPlayerID());
		Player victim = getPlayer(pkm.getVictimID());
		Weapon weapon = getWeapon(pkm.getWeaponID());
		
		if(killer == null || victim == null || weapon == null) return;
		
		BasicHUD hud = ingameState.getHUDState().getCurrentHUD();
		hud.addToKillContent(killer, victim, weapon);
		
		if(killer == ingameState.getPlayer() && hud instanceof PilotHUD) {
			PilotHUD pilotHUD = (PilotHUD)hud;
			int money = victim.getHunter().getDestroyMoney();
			pilotHUD.addToInfoContent(HUDState.getKillInfoText(victim), HUDState.KILL_INFO_COLORS, money);
		}
	}
	
	private void handleCMUseMessage(CMUseMessage cmum) {
		Player player = getPlayer(cmum.getPlayerID());
		player.getHunter().useDummyCounterMeasures();
	}
	
	@Override
	public void messageSent(Message message) {
		
	}
	
	@Override
	public void initClient(String ip) throws Exception {
		if(clientManager != null && clientManager.isConnected()) cleanup();
		
		clientManager = new ClientSyncManager(this);
		
		InetAddress address = InetAddress.getByName(ip);
		clientManager.connectToServer(address);
	}
	
	@Override
	public GamesTableWidget getLastGamesTableWidget() { return lastGameWidget; }
	
	@Override
	public void disconnectClient() { clientManager.endClient(); }
	
	@Override
	public SyncManager getSyncManager() { return clientManager; }
	
	protected void applyGameStartMessage(final GameStartMessage gsm) {
		GameTaskQueueManager.getManager().update(new Callable<Void>() {
			@Override
			public Void call() throws Exception {
				for(PlayerAddMessage pam : gsm.getPAMs()) {
					registerNewPlayer(pam);
				}
				
				for(SpawnStationSyncMessage sssm : gsm.getSpawnSyncMessages()) {
					applySpawnStationSyncMessage(sssm);
				}
				
				for(SquadUpdateMessage sum : gsm.getSUMs()) {
					applySquadUpdateMessage(sum);
				}
				
				for(ProcessCreateMessage pcm : gsm.getPCMs()) {
					applyProcessCreateMessage(pcm);
				}
				
				return null;
			}
		});
	}
	
	@Override
	public void registerNewPlayer(PlayerAddMessage pam) {
		super.registerNewPlayer(pam);
		
		if(pam.isHuman()) {
			Integer newPlayerInteger = Integer.valueOf(pam.getPlayerID());
			Short shortID = Short.valueOf(pam.getPlayerShortID());
			humanPlayers.put(newPlayerInteger, shortID);
			humanPlayersShort.put(shortID, newPlayerInteger);
		}
		clientManager.checkPlayerLessHunters(pam.getPlayerID());
	}
	
	protected void applySpawnStationSyncMessage(final SpawnStationSyncMessage sssm) {
		GameTaskQueueManager.getManager().update(new Callable<Void>() {
			@Override
			public Void call() throws Exception {
				int objectID = sssm.getObjectID();
				final SpawnStation station = (SpawnStation)objects.get(objectID);
				
				Player player = getPlayer(sssm.getPlayerID());
				if(player != null) {
					float amount = FastMath.abs(station.getCapturePoints() - sssm.getPoints());
					station.changeCapturePoints(amount, player);
				}
				
				return null;
			}
		});
	}
	
	@Override
	public void registerTransmittedShip(Ship ship) {
		int objectID = ship.getID();
		if(!objects.contains(objectID)) {
			objects.register(ship, objectID);
		}
		
		WeaponRequestMessage wrm = new WeaponRequestMessage(objectID, currentPlayerID);
		clientManager.sendMessageToServer(wrm);
	}
	
	@Override
	public void registerSyncObject(Synchronizable obj) {
		try {
			SynchronizeCreateMessage syncMessage = obj.getCreateMessage();
			if(syncMessage == null) return;
			
			syncMessage.setPlayerId(currentPlayerID);
			clientManager.getSyncManager().register(obj, syncMessage, SYNC_UPDATE_RATE);
		} catch(Exception e) { e.printStackTrace(); }
	}
	
	@Override
	public void registerWeaponFire(int weaponID, WeaponFire fire) {
		currentWeaponFireClientID++;
		if(currentWeaponFireClientID < 0) currentWeaponFireClientID = 1;
		clientWeaponFireMap.put(Integer.valueOf(currentWeaponFireClientID), fire);
		
		WeaponFireAddMessage wfam = new WeaponFireAddMessage(weaponID, fire.getDirection(), currentWeaponFireClientID);
		clientManager.sendMessageToServer(wfam);
	}
	
	@Override
	public void unregisterWeapon(Weapon weapon) {
		super.unregisterWeapon(weapon);
		sendWeaponRemoveMessage(weapon);	
	}
	
	@Override
	protected void sendWeaponAddMessage(Weapon weapon) {
		WeaponAddMessage weaponMessage = new WeaponAddMessage(weapon);
		clientManager.sendMessageToServer(weaponMessage);
	}
	
	private void sendWeaponRemoveMessage(Weapon weapon) {
		WeaponRemoveMessage weaponMessage = new WeaponRemoveMessage(weapon.getID());
		clientManager.sendMessageToServer(weaponMessage);
	}
	
	public void sendMessage(SimpleMessage cm) {
		clientManager.sendMessageToServer(cm);
	}
	
	@Override
	protected void sendPlayerUpdateMessage(PlayerUpdateMessage pum) {
		clientManager.sendMessageToServer(pum);
	}
	
	@Override
	public StatusListener getPlayerStatusListener(final int objectID) {
		return new StatusListener() {
			@Override
			public void valueChanged(StatusChangedEvent event) {
				
			}
		};
	}
	
	@Override
	protected void sendUpgradeMessage(UpgradeMessage um) {
		clientManager.sendMessageToServer(um);
	}
	
	@Override
	public void playerWantsTeamChange(Player player) {
		clientManager.sendMessageToServer(new TeamChangeWantMessage(player.getID()));
	}
	
	private void applyNearStationChangeMessage(NearStationChangeMessage message) {
		final Player player = getPlayer(message.getPlayerID());
		if(player == null) return;
		
		int stationID = message.getStationID();
		SpawnPosition spawnPos = null;
		if(stationID != 0) spawnPos = (SpawnPosition)objects.get(stationID);
		final SpawnPosition s = spawnPos;
		
		GameTaskQueueManager.getManager().update(new Callable<Void>() {
			@Override
			public Void call() throws Exception {
				player.changeNearSpawnPosition(s);
				return null;
			}
		});
		if(spawnPos instanceof SpawnStation) {
			SpawnStation station = (SpawnStation)spawnPos;
			station.addCapturer(player);
		}
	}
	
	@Override
	public void handeCollisionDamage(int hunterID, int damage) {
		CollisionDamageMessage cdm = new CollisionDamageMessage(hunterID, damage);
		clientManager.sendMessageToServer(cdm);
	}
	
	@Override
	protected SquadListener getSquadListener(Squad squad) {
		return new SquadListener() {
			@Override
			public void squadChanged(SquadChangedEvent event) {
				Squad squad = event.getSquad();
				if(squad != updatingSquad) {
					SquadUpdateMessage sum = new SquadUpdateMessage(squad);
					clientManager.sendMessageToServer(sum);
				}
			}
		};
	}
	
	@Override
	public void handleCMUse(Player player) {
		CMUseMessage cmum = new CMUseMessage(player.getID());
		clientManager.sendMessageToServer(cmum);
	}
	
	@Override
	public void sendChatMessage(String text, ChatType type) {
		clientManager.sendMessageToServer(createChatMessage(text, type));
	}
	
	@Override
	public HeatSeeker getFiredHeatSeeker(int clientWeaponFireID) {
		WeaponFire fire = clientWeaponFireMap.get(Integer.valueOf(clientWeaponFireID));
		if(fire != null) return (HeatSeeker)fire;
		return null;
	}

	@Override
	protected void handleNearSpawnStationChanged(NearStationChangedEvent event, int playerID) {}
	
	@Override
	protected void handleKillEvent(KillEvent event) {}
}